/**
 *
 */
package com.ozq.common.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

/**
 * <签名算法工具类>
 * <p>
 * <其它的第三方需要本工具共享>
 * 
 * @author 欧增奇
 * @version [版本号, 2017年8月16日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class SignAlgorithmUtils
{

    static Logger logger = LoggerFactory.getLogger(SignAlgorithmUtils.class);

    /**
     * 加密类型
     */
    private enum encryption
    {
        MD5, SHA1, SHA256
    }

    /**
     * 根据参数生成sign算法(会转码)
     * 
     * @param map
     *            参数（request.getParameterMap()）
     * @param encode
     *            是否编码（post不需要编码false，get需要编码true）
     * @return 返回生成的sign值，生成失败返回null
     */
    public static String createSignArray(Map<String, String[]> map, boolean encode)
    {

        Map<String, Object> tempMap = new HashMap<>();

        for (Entry<String, String[]> element : map.entrySet())
        {

            String key = element.getKey();
            if (StringUtils.equals(key, "ozq.si"))
            {
                continue;
            }

            String tempValue = "";
            String[] values = element.getValue();

            for (String value : values)
            {
                if (encode)
                    tempValue += toEncodeUTF8(value);
                else
                    tempValue += value;
            }

            if (encode)
                tempMap.put(toEncodeUTF8(key), tempValue);
            else
                tempMap.put(key, tempValue);
        }
        return createSign(tempMap);
    }

    /**
     * 根据参数生成sign算法
     * 
     * @param map
     *            参数
     * @return 返回生成的sign值，生成失败返回null
     */
    public static String createSign(Map<String, Object> map)
    {
        String sign = null;
        try
        {
            String tempKey = "";
            for (String key : map.keySet())
            {
                if (StringUtils.equals(key, "sign"))
                {
                    continue;
                }
                tempKey += (tempKey.length() > 0 ? "&" : "") + key;
            }
            String[] keys = tempKey.split("&");

            // 排序
            Arrays.sort(keys);

            // 拼接参数
            StringBuffer temp = new StringBuffer();
            boolean first = true;
            for (Object key : keys)
            {
                if (first)
                {
                    first = false;
                } else
                {
                    temp.append("&");
                }
                temp.append(URLEncoder.encode(String.valueOf(key), "UTF-8"));
                temp.append("=");
                temp.append(URLEncoder.encode(String.valueOf(map.get(key)), "UTF-8"));
            }

            // logger.debug("debug 原sign:{}", temp);

            // md5
            sign = getMD5(temp.toString());
            // 先位移
            sign = shift(sign, false, 3);
            // Base64
            sign = Base64Utils.encodeToString(sign.getBytes());

            // logger.debug("debug sign:{}", sign);
        } catch (NoSuchAlgorithmException e)
        {
            logger.error("error 创建sign编码MD5错误 e:{}", e.getMessage());
        } catch (UnsupportedEncodingException e)
        {
            logger.error("error 创建sign编码URLEncoder错误 e:{}", e.getMessage());
        }
        return sign;
    }

    /**
     * 字符串右/左位移
     * 
     * @param strSrc
     *            待位移的字符串
     * @param direction
     *            待位移的方向（true：左，false：右；默认：false）
     * @param index
     *            待位移的多少位
     * @return 返回位移好的字符串
     */
    public static String shift(String strSrc, boolean direction, int index)
    {

        StringBuffer sbu = new StringBuffer();
        char[] chars = strSrc.toCharArray();
        for (int i = 0; i < chars.length; i++)
        {
            int tempChar = (int) chars[i];
            if (BooleanUtils.isFalse(direction))
            {
                tempChar = tempChar >> index;
            } else
            {
                tempChar = tempChar << index;
            }
            sbu.append((char) tempChar);
        }
        return sbu.toString();
    }

    /**
     * 反转字符串，把字符串反转过来
     * 
     * @param strSrc
     *            待反正的字符串
     * @return 反转完成的字符串
     */
    public static String reChange(String strSrc)
    {
        char[] strSrcs = strSrc.toCharArray();
        for (int i = 0; i < strSrc.length() / 2; i++)
        {
            char temp = strSrcs[i];
            strSrcs[i] = strSrcs[strSrc.length() - 1 - i];
            strSrcs[strSrcs.length - 1 - i] = temp;
        }
        return String.valueOf(strSrcs);
    }

    /**
     * 平移字符串
     * <p>
     * 注意：<br>
     * 1.num或len当大于字符长度不做处理<br>
     * 2.num当等于字符长度在最后插入
     * 
     * @param strSrc
     *            待平移的字符串
     * @param num
     *            平移的位置（插入的位置）
     * @param len
     *            平移的长度（从字符串左边0开始算）
     * @return 返回平移后的结果
     */
    public static String translation(String strSrc, int num, int len)
    {
        char[] charStrSrc = strSrc.toCharArray();

        if (num > charStrSrc.length || len > charStrSrc.length)
        {
            return strSrc;
        }

        reverse(charStrSrc, 0, len - 1);
        reverse(charStrSrc, len, num - 1);
        reverse(charStrSrc, 0, num - 1);
        return String.valueOf(charStrSrc);

    }

    /**
     * 替换字符的位置
     * 
     * @param charStrSrc
     *            字符
     * @param left
     *            左边的位置
     * @param right
     *            右边的位置
     */
    private static void reverse(char[] charStrSrc, int left, int right)
    {
        while(left < right)
        {
            swap(charStrSrc, left, right);
            left++;
            right--;
        }
    }

    /**
     * 交换字符的位置
     * 
     * @param charStrSrc
     *            字符
     * @param left
     *            左边的位置
     * @param right
     *            右边的位置
     */
    private static void swap(char[] charStrSrc, int left, int right)
    {
        char temp = charStrSrc[left];
        charStrSrc[left] = charStrSrc[right];
        charStrSrc[right] = temp;
    }

    /**
     * 根据字符串生成md5的值
     * 
     * @param value
     *            待md5的值
     * @return md5后的结果
     * @throws NoSuchAlgorithmException
     */
    public static String getMD5(String value) throws NoSuchAlgorithmException
    {
        return encrypt(value, encryption.MD5);
    }

    /**
     * 根据字符串生成SHA-256的值
     * 
     * @param value
     *            待SHA-256的值
     * @return SHA-256后的结果
     * @throws NoSuchAlgorithmException
     */
    public static String getSHA256(String value) throws NoSuchAlgorithmException
    {
        return encrypt(value, encryption.SHA256);
    }

    /**
     * 根据字符串生成SHA-1的值
     * 
     * @param value
     *            待SHA-1的值
     * @return SHA-1后的结果
     * @throws NoSuchAlgorithmException
     */
    public static String getSHA1(String value) throws NoSuchAlgorithmException
    {
        return encrypt(value, encryption.SHA1);
    }

    /**
     * 对字符串加密,加密算法使用MD5,SHA-1,SHA-256,默认使用SHA-256
     * 
     * @param strSrc
     *            要加密的字符串
     * @param encryption
     *            加密类型
     * @return 加密后的string
     * @throws NoSuchAlgorithmException
     */
    private static String encrypt(String strSrc, encryption encryption) throws NoSuchAlgorithmException
    {

        // 类型
        String encName = "SHA-256";
        switch (encryption)
        {
            case MD5:
                encName = "MD5";
                break;
            case SHA1:
                encName = "SHA-1";
                break;
            case SHA256:
                encName = "SHA-256";
                break;

            default:
                encName = "SHA-256";
                break;
        }

        String strDes = null;
        byte[] bt = strSrc.getBytes();
        MessageDigest md = MessageDigest.getInstance(encName);
        md.update(bt);
        strDes = bytes2Hex(md.digest()); // to HexString
        return strDes;
    }

    /**
     * byte转string
     * 
     * @param bts
     *            待转换的byte
     * @return 转换后的string
     */
    private static String bytes2Hex(byte[] bts)
    {
        String des = "";
        String tmp = null;
        for (int i = 0; i < bts.length; i++)
        {
            tmp = (Integer.toHexString(bts[i] & 0xFF));
            if (tmp.length() == 1)
            {
                des += "0";
            }
            des += tmp;
        }
        return des;
    }

    /**
     * 判断字符串的编码格式
     *
     * @param str
     *            待判断的字符串
     * @return 返回编码格式，没有匹配到返回""
     */
    public static String toEncodeUTF8(String str)
    {

        // String encodes[] = new String[] { "UTF-8", "ISO-8859-1", "GBK", "GB2312", "GB18030", "Big5", "Unicode",
        // "ASCII","UTF-16", "GB13000", "UNICODE", "UTF-32" };
        try
        {
            String sysEncode = Charset.defaultCharset().name();
            // 本身的编码格式
            if (StringUtils.equalsIgnoreCase("UTF-8", sysEncode) || StringUtils.equalsIgnoreCase("ISO-8859-1", sysEncode))
            {
                return new String(str.getBytes("ISO-8859-1"), "UTF-8");
            } else
            {// 其他的编码格式
                return new String(str.getBytes("UTF-8"), "UTF-8");
            }
        } catch (UnsupportedEncodingException e)
        {
            logger.error("error 编码转换错误 e:{}", e.getMessage());
        }
        return str;
    }
}
